/*
 * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.print;

import java.io.OutputStream;

import java.util.ArrayList;
import java.util.Iterator;

import javax.print.DocFlavor;

import sun.awt.AppContext;
import java.util.ServiceLoader;
import java.util.ServiceConfigurationError;

/**
 * A <code>StreamPrintServiceFactory</code> is the factory for
 * {@link StreamPrintService} instances,
 * which can print to an output stream in a particular
 * document format described as a mime type.
 * A typical output document format may be Postscript(TM).
 * <p>
 * This class is implemented by a service and located by the
 * implementation using the
 * <a href="../../../technotes/guides/jar/jar.html#Service Provider">
 * SPI JAR File specification</a>.
 * <p>
 * Applications locate instances of this class by calling the
 * {@link #lookupStreamPrintServiceFactories(DocFlavor, String)} method.
 * <p>
 * Applications can use a <code>StreamPrintService</code> obtained from a
 * factory in place of a <code>PrintService</code> which represents a
 * physical printer device.
 */

public abstract class StreamPrintServiceFactory {

  static class Services {

    private ArrayList listOfFactories = null;
  }

  private static Services getServices() {
    Services services =
        (Services) AppContext.getAppContext().get(Services.class);
    if (services == null) {
      services = new Services();
      AppContext.getAppContext().put(Services.class, services);
    }
    return services;
  }

  private static ArrayList getListOfFactories() {
    return getServices().listOfFactories;
  }

  private static ArrayList initListOfFactories() {
    ArrayList listOfFactories = new ArrayList();
    getServices().listOfFactories = listOfFactories;
    return listOfFactories;
  }

  /**
   * Locates factories for print services that can be used with
   * a print job to output a stream of data in the
   * format specified by {@code outputMimeType}.
   * <p>
   * The {@code outputMimeType} parameter describes the document type that
   * you want to create, whereas the {@code flavor} parameter describes the
   * format in which the input data will be provided by the application
   * to the {@code StreamPrintService}.
   * <p>
   * Although null is an acceptable value to use in the lookup of stream
   * printing services, it's typical to search for a particular
   * desired format, such as Postscript(TM).
   * <p>
   *
   * @param flavor of the input document type - null means match all types.
   * @param outputMimeType representing the required output format, used to identify suitable stream
   * printer factories. A value of null means match all formats.
   * @return - matching factories for stream print service instance, empty if no suitable factories
   * could be located.
   */
  public static StreamPrintServiceFactory[]
  lookupStreamPrintServiceFactories(DocFlavor flavor,
      String outputMimeType) {

    ArrayList list = getFactories(flavor, outputMimeType);
    return (StreamPrintServiceFactory[])
        (list.toArray(new StreamPrintServiceFactory[list.size()]));
  }

  /**
   * Queries the factory for the document format that is emitted
   * by printers obtained from this factory.
   *
   * @return the output format described as a mime type.
   */
  public abstract String getOutputFormat();

  /**
   * Queries the factory for the document flavors that can be accepted
   * by printers obtained from this factory.
   *
   * @return array of supported doc flavors.
   */
  public abstract DocFlavor[] getSupportedDocFlavors();

  /**
   * Returns a <code>StreamPrintService</code> that can print to
   * the specified output stream.
   * The output stream is created and managed by the application.
   * It is the application's responsibility to close the stream and
   * to ensure that this Printer is not reused.
   * The application should not close this stream until any print job
   * created from the printer is complete. Doing so earlier may generate
   * a <code>PrinterException</code> and an event indicating that the
   * job failed.
   * <p>
   * Whereas a <code>PrintService</code> connected to a physical printer
   * can be reused,
   * a <code>StreamPrintService</code> connected to a stream cannot.
   * The underlying <code>StreamPrintService</code> may be disposed by
   * the print system with
   * the {@link StreamPrintService#dispose() dispose} method
   * before returning from the
   * {@link DocPrintJob#print(Doc, javax.print.attribute.PrintRequestAttributeSet) print}
   * method of <code>DocPrintJob</code> so that the print system knows
   * this printer is no longer usable.
   * This is equivalent to a physical printer going offline - permanently.
   * Applications may supply a null print stream to create a queryable
   * service. It is not valid to create a PrintJob for such a stream.
   * Implementations which allocate resources on construction should examine
   * the stream and may wish to only allocate resources if the stream is
   * non-null.
   * <p>
   *
   * @param out destination stream for generated output.
   * @return a PrintService which will generate the format specified by the DocFlavor supported by
   * this Factory.
   */
  public abstract StreamPrintService getPrintService(OutputStream out);


  private static ArrayList getAllFactories() {
    synchronized (StreamPrintServiceFactory.class) {

      ArrayList listOfFactories = getListOfFactories();
      if (listOfFactories != null) {
        return listOfFactories;
      } else {
        listOfFactories = initListOfFactories();
      }

      try {
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedExceptionAction() {
              public Object run() {
                Iterator<StreamPrintServiceFactory> iterator =
                    ServiceLoader.load
                        (StreamPrintServiceFactory.class).iterator();
                ArrayList lof = getListOfFactories();
                while (iterator.hasNext()) {
                  try {
                    lof.add(iterator.next());
                  } catch (ServiceConfigurationError err) {
                                     /* In the applet case, we continue */
                    if (System.getSecurityManager() != null) {
                      err.printStackTrace();
                    } else {
                      throw err;
                    }
                  }
                }
                return null;
              }
            });
      } catch (java.security.PrivilegedActionException e) {
      }
      return listOfFactories;
    }
  }

  private static boolean isMember(DocFlavor flavor, DocFlavor[] flavors) {
    for (int f = 0; f < flavors.length; f++) {
      if (flavor.equals(flavors[f])) {
        return true;
      }
    }
    return false;
  }

  private static ArrayList getFactories(DocFlavor flavor, String outType) {

    if (flavor == null && outType == null) {
      return getAllFactories();
    }

    ArrayList list = new ArrayList();
    Iterator iterator = getAllFactories().iterator();
    while (iterator.hasNext()) {
      StreamPrintServiceFactory factory =
          (StreamPrintServiceFactory) iterator.next();
      if ((outType == null ||
          outType.equalsIgnoreCase(factory.getOutputFormat())) &&
          (flavor == null ||
              isMember(flavor, factory.getSupportedDocFlavors()))) {
        list.add(factory);
      }
    }

    return list;
  }

}
